﻿using DotNetCommon;
using DotNetCommon.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DBUtil.Provider.SqlServer;

#region sqlserver 表名规则总结
//参照: https://blog.csdn.net/u010476739/article/details/152324976
// 总结: 当 sqlserver 解析的时候,
// 当开头遇到了 '[', 则开启了分割边界
//     接着往后走, 只要不遇到 ']' 就表示是表名(即使是遇到换行符或者又是'[')
//     直到遇到 ']' 则再往后看一个
//         如果又是 ']' 则合并 ']]' 为一个普通的 ']'
//         如果不是 ']' 则结束了分割边界
//
// 看下面的实战演示:
//-- 正常情况
//create table [test](id int) -- test
//
//-- 换行符会当成表名的一部分, 建好表后在ssms中完全看不出来, 只能用过下面的查询验证
//create table [test
//ok](id int) -- test换行符ok
//select * from [test
//ok]
//
//-- 左侧第一个 '[' 认为是边界, 第二个认为是表名一部分, 最后一个认为是边界
//create table [[test123](id int) -- [test123

//-- 左侧第一个 '[' 认为是边界, 第二、三个认为是表名的一部分, 最后一个认为是边界
//create table [[[test456](id int) -- [[test456

//-- 左侧第一个 '[' 认为是边界, 第二、三、四个认为是表名的一部分, 最后一个认为是边界
//create table [[[[test789](id int) -- [[[test789

//-- 左侧第一个 '[' 认为是边界, 中间的 '[' 认为是表名一部分, 最后一个认为是边界
//create table [te[st012](id int) -- te[st012

//-- 左侧第一个 '[' 认为是边界, 中间的两个 '[' 认为是表名一部分, 最后一个认为是边界
//create table [te[[st159](id int) -- te[[st159

//-- 左侧第一个 '[' 认为是边界, 中间的 ']' 认为是结束边界, 所以报错
//create table [te]st](id int) -- error

//-- 左侧第一个 '[' 认为是边界, 中间的 ']]' 被当做一个 ']' 是表名一部分，最后一个认为是边界 
//create table [te]]st247](id int) -- te]st247

//-- 左侧第一个 '[' 认为是边界, 中间的 ']]' 被当做一个 ']' 是表名一部分，又紧接着出现的 ']' 认为是结束边界, 所以报错
//create table [te]]]st](id int) -- error

//-- 左侧第一个 '[' 认为是边界, 中间的 ']]]]' 被当做 ']]' 是表名一部分，最后一个认为是边界 
//create table [te]]]]st358](id int) -- te]]st358

//-- 左侧第一个 '[' 认为是边界, 中间的 ']]]]' 被当做 ']]' 是表名一部分，又紧接着出现的 ']' 认为是结束边界, 所以报错
//create table [te]]]]]st](id int) -- error

//-- 左侧第一个 '[' 认为是边界, 后面的 ']]' 被当做 ']' 是表名一部分，没有结束边界, 所以报错
//create table [test]](id int) -- error

//-- 左侧第一个 '[' 认为是边界, 后面的 ']]' 被当做 ']' 是表名一部分，最后一个认为是边界 
//create table [test2468]]](id int) -- test2468]

//-- 左侧第一个 '[' 认为是边界, 后面的 '[[[' 被当做 '[[[' 是表名一部分，后面的 ']]]]' 被当做 ']]' 是表名一部分, 最后一个是边界
//create table [[[[test3579]]]]](id int) -- [[[test3579]]
#endregion

public partial class SqlServerAccess : DBAccess
{
    #region 表名或标识符分析/解析
    private static readonly IReadOnlyList<string> _QuotedCharacters = ["[]", "\"\""];
    /// <inheritdoc />
    public override IReadOnlyList<string> QuotedCharacters => _QuotedCharacters;
    /// <inheritdoc />
    public override string AddQuote(string pureName)
    {
        if (pureName.IsNullOrEmpty()) return pureName;
        return $"[{pureName.Replace("]", "]]")}]";
    }
    /// <inheritdoc />
    public override IEnumerable<string> AnalysisQuotedName(string quotedName)
    {
        var len = quotedName?.Length ?? 0;
        if (len == 0) yield break;

        var currentIdx = 0;

        void readWhiteSpaceOrDot()
        {
            while (currentIdx < len && (char.IsWhiteSpace(quotedName[currentIdx]) || quotedName[currentIdx] == '.')) currentIdx++;
        }
        string readQuotedSeg(char endChar)
        {
            var startIdx = currentIdx;
            var end = false;
            List<int> skipIdxes = null;

            while (currentIdx < len)
            {
                if (quotedName[currentIdx] != endChar)
                {
                    //没找到结束符号
                    currentIdx++;
                    continue;
                }

                //找到了结束符号
                //需要再判断一步: 两个双引号合并成一个
                currentIdx++;
                if (currentIdx >= len || quotedName[currentIdx] != endChar)
                {
                    //不一样 不能合并
                    currentIdx--;
                    end = true;
                    break;
                }
                //两个双引号, 忽略后面的一个
                skipIdxes ??= new List<int>(8);
                skipIdxes.Add(currentIdx);
                currentIdx++;
            }

            if (!end) throw new Exception($"对象名格式错误(结尾处缺少符号: 【{endChar}】): {quotedName}");
            if (skipIdxes == null)
            {
                //没有合并 简单情况如: "testdb"."table"
                return quotedName[startIdx..currentIdx];
            }
            else
            {
                //有合并情况 如: "tes\"\"tdb"."table"
                var tmpList = new List<char>(64);
                for (int i = startIdx; i < currentIdx; i++)
                {
                    if (skipIdxes.Contains(currentIdx)) continue;
                    tmpList.Add(quotedName[i]);
                }
                return new string([.. tmpList]);
            }
        }

        string readUnQuotedSeg()
        {
            var startIdx = currentIdx;
            while (currentIdx < len)
            {
                if (quotedName[currentIdx] == '.' || char.IsWhiteSpace(quotedName[currentIdx]))
                {
                    return quotedName[startIdx..currentIdx];
                }
                currentIdx++;
            }
            return quotedName[startIdx..];
        }

        //消耗左侧空白及dot(.)
        readWhiteSpaceOrDot();
        if (currentIdx >= len) yield break;

        //读取第一次
        var curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '[')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar == '"' ? curChar : ']');
            currentIdx++;//读取停止的时候指针一定对着最后一个 ` 或 " 所以要跳一个
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
            //读取停止的时候指针一定对着 . 或空白符
        }

        readWhiteSpaceOrDot();//读取停止的时候指针一定不会对着空白符或 .
        if (currentIdx >= len) yield break;

        //读取第二次
        curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '[')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar == '"' ? curChar : ']');
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
        }

        readWhiteSpaceOrDot();//读取停止的时候指针一定不会对着空白符或 .
        if (currentIdx >= len) yield break;

        //读取第三次
        curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '[')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar == '"' ? curChar : ']');
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
        }

        readWhiteSpaceOrDot();//读取停止的时候指针一定不会对着空白符或 .
        if (currentIdx >= len) yield break;

        //读取第四次
        curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '[')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar == '"' ? curChar : ']');
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
        }
    }
    /// <inheritdoc />
    public override TableName ParseQuotedName(string quotedName)
    {
        if (quotedName.IsNullOrWhiteSpace()) return null;
        //基础解析
        var segArr = AnalysisQuotedName(quotedName).ToArray();
        if (segArr.IsNullOrEmpty()) throw new Exception($"解析对象名称失败: {quotedName}");

        if (segArr.Length == 1)
        {
            var name = segArr[0];
            AssertUtil.NotNullOrWhiteSpace(name);
            return new TableName(EnumDBType.SqlServer, name);
        }
        else
        {
            //[testdb].[table1] or  [otherName].[testdb].[table1]
            //sqlsever 中 可能有三级甚至四级
            var length = segArr.Length;
            var schemaName = segArr[length - 2];
            var name = segArr[length - 1];
            AssertUtil.NotNullOrWhiteSpace(schemaName);
            AssertUtil.NotNullOrWhiteSpace(name);
            var objName = new TableName(
                EnumDBType.SqlServer, name: name, schemaName: schemaName);
            if (length > 2)
            {
                var dbName = segArr[length - 3];
                AssertUtil.NotNullOrWhiteSpace(dbName);
                objName.SetDataBaseName(dbName);
            }
            if (length > 3)
            {
                var dblinkName = segArr[length - 4];
                AssertUtil.NotNullOrWhiteSpace(dblinkName);
                objName.SetDBLinkName(dblinkName);
            }
            return objName;
        }
    }

    /// <inheritdoc />
    public override TableName CreateTableName()
    {
        return new TableName(EnumDBType.SqlServer);
    }
    #endregion

    /// <inheritdoc />
    public override string EscapeString(string str) => str?.Replace("'", "''");
}
